/* tslint:disable */
/* eslint-disable */
{{>licenseInfo}}

import { Configuration } from "./configuration";
import { RequiredError, RequestArgs } from "./base";
import axios, { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
import { requestAfterHook } from "./requestAfterHook";
import { requestBeforeUrlHook } from "./requestBeforeUrlHook";
import { readableStreamToString, {{clientName}}Error, parseIfJson } from "./error";
{{#hasOAuthMethods}}
import { jwtDecode } from "jwt-decode";
{{/hasOAuthMethods}}
{{#withNodeImports}}
import { URL, URLSearchParams } from 'url';
{{/withNodeImports}}

/**
 *
 * @export
 */
export const DUMMY_BASE_URL = 'https://example.com'

/**
 *
 * @throws {RequiredError}
 * @export
 */
export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) {
    if (paramValue === null || paramValue === undefined) {
        throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`);
    }
}

/**
 *
 * @export
 */
export const setApiKeyToObject = async function ({
  object,
  key,
  type,
  keyParamName,
  configuration,
  prefix
}: {
  object: any
  key?: string
  type?: "Cookie"
  keyParamName: string
  configuration?: Configuration
  prefix?: string
}) {
  key = key ? key : keyParamName
  let apiKey: string | null | undefined = null
  if (configuration && configuration.apiKey) {
    if (typeof configuration.apiKey === 'function')
      apiKey = await configuration.apiKey(keyParamName)
    else if (typeof configuration.apiKey === 'string')
      apiKey = configuration.apiKey
    else if (typeof configuration.apiKey === 'object') {
      if (keyParamName in configuration.apiKey)
        apiKey = configuration.apiKey[keyParamName]
    } else
      throw Error(
        `Unexpected type ${typeof configuration.apiKey} for Configuration.apiKey`
      )
  }
  if (!apiKey) return
  object[key] = prefix !== undefined ? `${prefix}${apiKey}` : apiKey
  if (type === "Cookie")
    object[key] = `${keyParamName}=${object[key]}`
}

/**
 *
 * @export
 */
export const setBasicAuthToObject = function (object: any, configuration?: Configuration) {
    if (configuration && (configuration.username || configuration.password)) {
        object["auth"] = { username: configuration.username, password: configuration.password };
    }
}

/**
 *
 * @export
 */
export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) {
    if (configuration && configuration.accessToken) {
        const accessToken = typeof configuration.accessToken === 'function'
            ? await configuration.accessToken()
            : await configuration.accessToken;
        object["Authorization"] = "Bearer " + accessToken;
    }
}

{{#hasOAuthMethods}}
/**
 *
 * @export
 */
export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) {
    {{#hasAuthMethods}}
    {{#authMethods}}
    {{#-first}}
    if (configuration === undefined) return;
    // Sets the OAuth2 authentication header for the request and saves the token for the next request
    const authenticate = async () => {
        if (configuration.oauthClientId && configuration.oauthClientSecret && configuration.accessToken === undefined) {
            const tokenResponse = await wrapAxiosRequest(async () => {
                {{#isAuthorizationOrTokenUrlRelative}}
                if (configuration.basePath === undefined) {
                    throw new Error("basePath must be set to use the oauth2 authentication");
                }
                const url = configuration.oauthTokenUrl ?? removeTrailingSlash(configuration.basePath) + "{{{authorizationOrTokenUrl}}}"
                {{/isAuthorizationOrTokenUrlRelative}}
                {{^isAuthorizationOrTokenUrlRelative}}
                const url = configuration.oauthTokenUrl ?? "{{{absoluteAuthorizationOrTokenUrl}}}"
                {{/isAuthorizationOrTokenUrlRelative}}
                const oauthResponse = await axios.request({
                    url,
                    method: "POST",
                    headers: {
                        "content-type": "application/x-www-form-urlencoded",
                    },
                    data: `grant_type=client_credentials&client_id=${configuration.oauthClientId}&client_secret=${configuration.oauthClientSecret}`,
                });
                const json = await oauthResponse.data;

                // we expect the token value to be in the property "access_token". if that property does not exist
                // on the response, then return the value of any property with the substring "token" in its name
                const token = json.access_token ?? Object.keys(json).reduce((acc, key) => {
                    if (key.toLowerCase().includes("token")) {
                        return json[key];
                    }
                    return acc;
                }, undefined);
                return {"token": token, "expires_in": json.expires_in };
            })
            if (tokenResponse === undefined || tokenResponse.token === undefined)
                throw new Error("Token not found in OAuth response")
            configuration.accessToken = tokenResponse.token
            configuration.accessTokenExpiresIn = tokenResponse.expires_in !== undefined ? (Date.now() / 1000) + tokenResponse.expires_in : undefined
        }
    }
    {{/-first}}
    {{/authMethods}}
    {{/hasAuthMethods}}
    const getToken = async () => {
      return typeof configuration.accessToken === "function"
        ? await configuration.accessToken(name, scopes)
        : await configuration.accessToken;
    }
    const previousToken = await getToken()

    /**
     * Authenticate if the token is not set or if it is expired
     */
    if (previousToken === undefined) {
        await authenticate();
    } else {
        // check that the token is still valid
        let decodedToken = undefined;
        const currentTime = Date.now() / 1000;
        try { // If token is jwt
            decodedToken = jwtDecode(previousToken);
        } catch (e) {}
        if (decodedToken !== undefined && decodedToken.exp !== undefined && decodedToken.exp < currentTime
            || configuration.accessTokenExpiresIn !== undefined && configuration.accessTokenExpiresIn < currentTime) {

            configuration.accessToken = undefined;
            configuration.accessTokenExpiresIn = undefined;
            await authenticate();
        }
    }

    /**
     * Set the token in the request
     */
    const token = await getToken()
    if (token !== undefined) {
        object["Authorization"] = "Bearer " + token;
    }
}

{{/hasOAuthMethods}}
function setFlattenedQueryParams(urlSearchParams: URLSearchParams, parameter: any, key: string = ""): void {
    if (typeof parameter === "object") {
        if (Array.isArray(parameter)) {
            (parameter as any[]).forEach(item => setFlattenedQueryParams(urlSearchParams, item, key));
        }
        else {
            Object.keys(parameter).forEach(currentKey =>
                setFlattenedQueryParams(urlSearchParams, parameter[currentKey], `${key}${key !== '' ? '.' : ''}${currentKey}`)
            );
        }
    }
    else {
        if (urlSearchParams.has(key)) {
            urlSearchParams.append(key, parameter);
        }
        else {
            urlSearchParams.set(key, parameter);
        }
    }
}

/**
 *
 * @export
 */
export const setSearchParams = function (url: URL, ...objects: any[]) {
    const searchParams = new URLSearchParams(url.search);
    setFlattenedQueryParams(searchParams, objects);
    url.search = searchParams.toString();
}

/**
 *
 * @export
 */
export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) {
    const nonString = typeof value !== 'string';
    const needsSerialization = nonString && configuration && configuration.isJsonMime
        ? configuration.isJsonMime(requestOptions.headers['Content-Type'])
        : nonString;
    return needsSerialization
        ? JSON.stringify(value !== undefined ? value : {})
        : (value || "");
}

/**
 *
 * @export
 */
export const toPathString = function (url: URL) {
    return removeTrailingSlash(url.pathname) + url.search + url.hash
}

/**
 * remove trailing slash from string
 */
export const removeTrailingSlash = function (url: string) {
    return url.replace(/\/$/, "");
}

/**
 * Wrap an axios request in a try/catch block to catch network errors and parse the response body
 */
async function wrapAxiosRequest<R>(makeRequest: () => Promise<R>): Promise<R> {
    const maxAttempts = {{rateLimitRetryMaxAttempts}};
    let attempt = 0;
    let delay = {{rateLimitRetryInitialDelay}};
    while (attempt < maxAttempts) {
        try {
            return await makeRequest();
        } catch (e) {
            if (e instanceof AxiosError && e.isAxiosError) {
                if (e.response?.status == 429) {
                    attempt++;
                    console.log(`429 error encountered, retrying in ${delay / 1000} seconds...`);
                    await new Promise(resolve => setTimeout(resolve, delay));
                    delay *= 2;
                    continue;
                }
                try {
                    const responseBody =
                        e.response?.data instanceof ReadableStream
                        ? await readableStreamToString(e.response.data)
                        : e.response?.data
                    throw new {{clientName}}Error(e, parseIfJson(responseBody), e.response?.headers)
                } catch (innerError) {
                    if (innerError instanceof ReferenceError) {
                        // Got: "ReferenceError: ReadableStream is not defined"
                        // This means we are in a Node environment so just throw the original error
                        throw new {{clientName}}Error(e, e.response?.data, e.response?.headers)
                    }
                    if (innerError instanceof {{clientName}}Error) {
                        // Got "{{clientName}}Error" from the above try block
                        throw innerError;
                    }
                    // Something unexpected happened: propagate the error
                    throw e
                }
            }
            throw e
        }
    }
    throw new Error(`Request failed after ${maxAttempts} retries due to 429 (rate limit) errors.`);
}

/**
 *
 * @export
 */
export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) {
    return async <T = unknown, R = AxiosResponse<T>>(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
        requestBeforeUrlHook({axiosArgs, basePath, configuration})
        const url = (configuration?.basePath || basePath) + axiosArgs.url
        await requestAfterHook({axiosArgs, basePath, url, configuration})
        return wrapAxiosRequest(async () => await axios.request<T, R>({ ...axiosArgs.options, url }));
    };
}

export function isBrowser() {
    return typeof window !== "undefined"
}
